﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DotNetExtensions.AspNet.Net
{
    public class Telnet
    {
        private TcpClient tcpClient;
        private NetworkStream networkStream;
        private int bufferSize = 1024;
        public bool Connected
        {
            get { return tcpClient != null && tcpClient.Connected; }
        }

        public string Connect(string hostname, int port)
        {
            string result;
            try
            {
                tcpClient = new TcpClient(hostname, port);
                networkStream = tcpClient.GetStream();
                result = Negotiate();
            }
            catch (Exception e)
            {
                result = e.Message;
            }
            return result;
        }
        public bool TryConnect(string hostname, int port)
        {
            try
            {
                tcpClient = new TcpClient();
                tcpClient.ReceiveTimeout = 100;
                tcpClient.SendTimeout = 100;
                tcpClient.Connect(hostname, port);
                return tcpClient.Connected;
            }
            catch
            {
                return false;
            }
            finally
            {
                if (tcpClient != null && tcpClient.Connected) tcpClient.Close();
            }
        }

        public Task Listening(string hostname, int port, Action<bool> action, int timeout = 5000, CancellationToken token = default(CancellationToken))
        {
            return Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    if (token.IsCancellationRequested) break;
                    action?.Invoke(TryConnect(hostname, port));
                    Thread.Sleep(timeout);
                }
            }, token);
        }

        public string Connect(string hostname, int port, string username, string password, int waitTime)
        {
            string result = Connect(hostname, port);
            if (Connected && result.EndsWith("login"))
            {
                Send(username, waitTime);
                result = Receive();
                if (result.EndsWith("password"))
                {
                    Send(password, waitTime);
                    result = Receive();
                    if (!result.EndsWith("<"))
                    {
                        tcpClient.Close();
                        result = "Logon Error";
                    }
                }
            }
            return result;
        }

        public string Receive()
        {
            StringBuilder result = new StringBuilder();
            if (Connected && networkStream.CanRead)
            {
                byte[] buff = new byte[bufferSize];
                int numberOfRead = 0;
                do
                {
                    numberOfRead = networkStream.Read(buff, 0, bufferSize);
                    result.AppendFormat("{0}", Encoding.ASCII.GetString(buff, 0, numberOfRead));
                }
                while (networkStream.DataAvailable);
            }
            return result.ToString().Trim();
        }

        public void Send(string cmd, int waitTime)
        {
            if (Connected && networkStream.CanWrite)
            {
                cmd += "\r\n";
                byte[] buff = Encoding.ASCII.GetBytes(cmd);
                networkStream.Write(buff, 0, buff.Length);
                System.Threading.Thread.Sleep(waitTime);
            }
        }

        public void Close()
        {
            tcpClient.Close();
        }

        private string Negotiate()
        {
            string result = string.Empty;
            if (Connected)
            {
                while (true)
                {
                    byte[] rev = ReceiveBytes();
                    result = Encoding.ASCII.GetString(rev).Trim();
                    if (result.EndsWith("login"))
                    {
                        break;
                    }
                    int count = rev.Length / 3;
                    for (int i = 0; i < count; i++)
                    {
                        int iac = rev[i * 3];
                        int cmd = rev[i * 3 + 1];
                        int value = rev[i * 3 + 2];
                        if (((int)Verbs.IAC) != iac)
                        {
                            continue;
                        }
                        switch (cmd)
                        {
                            case (int)Verbs.DO:
                                networkStream.WriteByte((byte)iac);
                                networkStream.WriteByte(value == (int)Options.RD ? (byte)Verbs.WILL : (byte)Verbs.WONT);
                                networkStream.WriteByte((byte)value);
                                break;
                            case (int)Verbs.DONT:
                                networkStream.WriteByte((byte)iac);
                                networkStream.WriteByte((byte)Verbs.WONT);
                                networkStream.WriteByte((byte)value);
                                break;
                            case (int)Verbs.WILL:
                                networkStream.WriteByte((byte)iac);
                                networkStream.WriteByte(value == (int)Options.SGA ? (byte)Verbs.DO : (byte)Verbs.DONT);
                                networkStream.WriteByte((byte)value);
                                break;
                            case (int)Verbs.WONT:
                                networkStream.WriteByte((byte)iac);
                                networkStream.WriteByte((byte)Verbs.DONT);
                                networkStream.WriteByte((byte)value);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            return result;
        }

        private byte[] ReceiveBytes()
        {
            byte[] result = new byte[0];
            byte[] buff = new byte[bufferSize];
            int numberOfRead = 0;
            if (Connected && networkStream.CanRead)
            {
                numberOfRead = networkStream.Read(buff, 0, bufferSize);
                result = new byte[numberOfRead];
                Array.Copy(buff, result, numberOfRead);
            }
            return result;
        }
    }
    enum Verbs
    {
        WILL = 251,
        WONT = 252,
        DO = 253,
        DONT = 254,
        IAC = 255
    }
    enum Options
    {
        RD = 1,
        SGA = 3
    }
}
